import { Editor, Element as SlateElement, Transforms, Range } from 'slate';
import {
  ElementType,
  TextType,
  CustomElement,
  ListType,
  CustomText,
  LinkElement,
  IndentElementData,
  IndentElement,
  IndentType,
  UnorderedListElement,
  BulletedListElement,
} from './slate';

import { MAX_INDENT_DATA, MIN_INDENT_DATA } from './share';

import { LIST_TYPES, getTopAncestors } from './share';

interface CommandFn {
  (editor: Editor, format: ElementType): boolean;
}

export const isBlockActive: CommandFn = (editor, format) => {
  const [match] = Editor.nodes(editor, {
    match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === format,
  });

  return !!match;
};

export const toggleBlock: CommandFn = (editor, format) => {
  const isActive = isBlockActive(editor, format);
  const isList = LIST_TYPES.includes(format as ListType);

  Transforms.unwrapNodes(editor, {
    match: (n) => LIST_TYPES.includes((!Editor.isEditor(n) && SlateElement.isElement(n) && n.type) as ListType),
    split: true,
  });

  const newProperties: Partial<SlateElement> = {
    type: isActive ? 'paragraph' : isList ? 'list-item' : format,
  };

  Transforms.setNodes(editor, newProperties);

  if (format === 'codeBlock') {
    Editor.addMark(editor, 'code', true);
  }

  if (!isActive && isList) {
    const block = { type: format, children: [] } as CustomElement;
    Transforms.wrapNodes(editor, block);
  }
  return true;
};

interface CommandTextFn {
  (editor: Editor, format: TextType, value?: any): boolean;
}

export const isMarkActive: CommandTextFn = (editor, format: TextType) => {
  const marks = Editor.marks(editor) as CustomText;
  return marks ? !!marks[format] : false;
};

export const toggleMark: CommandTextFn = (editor, format, value = true) => {
  const isActive = isMarkActive(editor, format, value);

  if (isActive) {
    Editor.removeMark(editor, format);
  } else {
    Editor.addMark(editor, format, value);
  }
  return true;
};

export const insertLink = (editor: Editor, url: string) => {
  if (editor.selection) {
    wrapLink(editor, url);
  }
};

export const isLinkActive = (editor: Editor) => {
  const [link] = Editor.nodes(editor, {
    match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'link',
  });
  return !!link;
};

export const unwrapLink = (editor: Editor) => {
  Transforms.unwrapNodes(editor, {
    match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'link',
  });
};

export const wrapLink = (editor: Editor, url: string) => {
  const { selection } = editor;
  const isCollapsed = selection && Range.isCollapsed(selection);
  const link: LinkElement = {
    type: 'link',
    children: isCollapsed ? [{ text: url }] : [],
  };

  if (isCollapsed) {
    Transforms.insertNodes(editor, link);
  } else {
    Transforms.wrapNodes(editor, link, { split: true });
    Transforms.collapse(editor, { edge: 'end' });
  }
};

export function toggleIndent(editor: Editor, indentType: IndentType) {
  const firstBlock = getTopAncestors(editor);
  if (!firstBlock || Editor.isEditor(firstBlock)) return;
  const change = indentType === 'left' ? -1 : 1;

  // 单独处理 有序列表和无序列表
  if (firstBlock.type === 'unordered-list' || firstBlock.type === 'bulleted-list') {
    if (indentType === 'right') {
      const newProperties: UnorderedListElement | BulletedListElement = {
        type: firstBlock.type,
        children: [firstBlock],
      };
      Transforms.wrapNodes(editor, newProperties);
    } else {
      Transforms.unwrapNodes(editor, {
        match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && (n.type === 'bulleted-list' || n.type === 'unordered-list'),
        split: true,
      });
      const firstNode = getTopAncestors(editor);
      if (!Editor.isEditor(firstNode) && firstNode.type === 'list-item') {
        const newProperties: Partial<SlateElement> = {
          type: 'paragraph',
        };
        Transforms.setNodes(editor, newProperties);
        Transforms.unwrapNodes(editor, {
          match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && (n.type === 'bulleted-list' || n.type === 'unordered-list'),
          split: true,
        });
      }
    }
    return;
  }

  if (firstBlock.type !== 'indent') {
    if (change === -1) return;
    const newProperties: IndentElement = {
      type: 'indent',
      data: MIN_INDENT_DATA,
      children: [firstBlock],
    };
    Transforms.wrapNodes(editor, newProperties);
  } else {
    if (firstBlock.data === MAX_INDENT_DATA && change === 1) {
      return;
    }
    if (firstBlock.data === MIN_INDENT_DATA && change === -1) {
      Transforms.unwrapNodes(editor, {
        match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'indent',
      });
    } else if (firstBlock.type === 'indent') {
      const newProperties: Partial<IndentElement> = {
        data: (firstBlock.data + change) as IndentElementData,
      };
      Transforms.setNodes(editor, newProperties, {
        match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'indent',
      });
    }
  }
}
